﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Data;
using System.Windows.Input;
using AzureDiagMon.Common.DataVirtualization;
using AzureDiagMon.Common.Dtos;
using AzureDiagMon.Common.Helpers;
using AzureDiagMon.Common.Interfaces.Services;
using AzureDiagMon.Common.Mvvm;
using AzureDiagMon.Common.Mvvm.Helpers;
using AzureDiagMon.Common.Mvvm.Interfaces;
using AzureDiagMon.ViewModels.Entities;
using AzureDiagMon.ViewModels.Properties;
using GalaSoft.MvvmLight.Command;
using Ninject;

namespace AzureDiagMon.ViewModels.Screens
{
    [ViewModel("LogViewerScreen", IsScreen = true, Order = 0)]
    public class LogViewerScreenViewModel : ViewModel
    {
        [Inject]
        public MainViewModel Main { get; private set; }

        #region Imported services

        [Inject]
        public IAzureAccessService AzureAccessService { get; private set; }

        [Inject]
        public IDataStoreService DataStoreService { get; set; }

        #endregion

        #region Data

        private int numberOfEntries;
        public int NumberOfEntries
        {
            get { return numberOfEntries; }
            set
            {
                numberOfEntries = value;
                RaisePropertyChanged("NumberOfEntries");
            }
        }

        private LogEntryItemsProvider logEntryItemsProvider;
        public LogEntryItemsProvider LogEntryItemsProvider
        {
            get { return logEntryItemsProvider; }
            set
            {
                logEntryItemsProvider = value;
                if (value == null)
                {
                    LogEntries = null;
                }
                else
                {
                    LogEntries = new AsyncVirtualizingCollection<WadLogEntryRowViewModel>(value, 300);
                    LogEntries.CollectionChanged += OnLogEntriesCollectionChanged;
                    LogEntries.PropertyChanged += OnLogEntriesPropertyChanged;
                    UpdateMessage();
                }

                RaisePropertyChanged(f => f.LogEntryItemsProvider);
            }
        }

        void OnLogEntriesPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsLoading")
            {

            }
        }

        void OnLogEntriesCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Reset)
                NumberOfEntries = LogEntries.Count;
        }

        private AsyncVirtualizingCollection<WadLogEntryRowViewModel> logEntries;
        public AsyncVirtualizingCollection<WadLogEntryRowViewModel> LogEntries
        {
            get { return logEntries; }
            set
            {
                logEntries = value;
                //SetupLogEntriesView();
                RaisePropertyChanged("LogEntries");
            }
        }

        private void SetupLogEntriesView()
        {
            //TODO: This solution is unusable because the items not downloaded 
            //LogEntriesView = CollectionViewSource.GetDefaultView(LogEntries);
            //if (LogEntriesView.CanSort)
            //{
            //    LogEntriesView.SortDescriptions.Add(new SortDescription("Timestamp", ListSortDirection.Ascending));
            //}
        }

        private ICollectionView logEntriesView;
        public ICollectionView LogEntriesView
        {
            get { return logEntriesView; }
            set
            {
                logEntriesView = value;
                RaisePropertyChanged("LogEntriesView");
            }
        }

        #endregion

        #region State

        private readonly EventFilter eventFilter = new EventFilter();

        private string toggleAutosyncButtonTitle = Resources.ToggleAutosyncButtonTitleEnableAutoSync;
        public string ToggleAutosyncButtonTitle
        {
            get { return toggleAutosyncButtonTitle; }
            set
            {
                toggleAutosyncButtonTitle = value;
                RaisePropertyChanged(f => f.ToggleAutosyncButtonTitle);
            }
        }

        private bool isLocalTimeMode = true;
        public bool IsLocalTimeMode
        {
            get { return isLocalTimeMode; }
            set
            {
                isLocalTimeMode = value;
                LoadData(true, false);
                RaisePropertyChanged("IsLocalTimeMode");
            }
        }

        private string toggleLocalTimeButtonTitle = Resources.ToggleLocalTimeButtonTitleShowUTC;
        public string ToggleLocalTimeButtonTitle
        {
            get
            {
                return toggleLocalTimeButtonTitle;
            }
            set
            {
                toggleLocalTimeButtonTitle = value;
                RaisePropertyChanged("ToggleLocalTimeButtonTitle");
            }
        }

        private const double DefaultRefreshInterval = 30;

        private double refreshInterval = DefaultRefreshInterval;
        public double RefreshInterval
        {
            get { return refreshInterval; }
            set
            {
                if (value <= 0)
                    refreshInterval = DefaultRefreshInterval;
                else
                    refreshInterval = value;

                Main.LogSyncService.Configuration.RefreshInterval = new TimeSpan(0, 0, 0, (int)refreshInterval);
                RaisePropertyChanged("RefreshInterval");
            }
        }

        private IEnumerable<WadLogEntryRowViewModel> selectedLogEntries;

        public IEnumerable<WadLogEntryRowViewModel> SelectedLogEntries
        {
            get { return selectedLogEntries; }
            set
            {
                selectedLogEntries = value;
                RaisePropertyChanged("SelectedLogEntries");
            }
        }

        private readonly DateFilter[] dateFilters = DateFilter.DefaultFilters;
        public DateFilter[] DateFilters
        {
            get { return dateFilters; }
        }

        private DateFilter selectedDateFilter;
        public DateFilter SelectedDateFilter
        {
            get { return selectedDateFilter; }
            set
            {
                selectedDateFilter = value;
                Main.LogSyncService.Configuration.DateFilter = value;
                UpdateEventFilter();
                RaisePropertyChanged("SelectedDateFilter");
            }
        }


        public DateTime? FilterStartDate
        {
            get { return SelectedDateFilter.CustomStartDate; }
            set
            {
                SelectedDateFilter.CustomStartDate = value;
                Main.LogSyncService.Configuration.DateFilter = SelectedDateFilter;
                UpdateEventFilter();
                RaisePropertyChanged("FilterStartDate");
            }
        }

        public DateTime? FilterEndDate
        {
            get { return SelectedDateFilter.CustomEndDate; }
            set
            {
                if (value != null)
                    value = value.Value.Add(new TimeSpan(23, 59, 59));
                SelectedDateFilter.CustomEndDate = value;
                Main.LogSyncService.Configuration.DateFilter = SelectedDateFilter;
                UpdateEventFilter();
                RaisePropertyChanged("FilterEndDate");
            }
        }

        #endregion

        #region Commands

        public ICommand ToggleAutosyncCommand { get; private set; }
        public ICommand SelectionChangedCommand { get; private set; }
        public ICommand ToggleLocalTimeCommand { get; private set; }
        public ICommand RefreshCommand { get; private set; }
        public ICommand LoadedEventCommand { get; private set; }
        public ICommand KeyDownEventCommand { get; private set; }

        private string timeModeLabel = Resources.TimeModeLabelLocalMode;
        public string TimeModeLabel
        {
            get { return timeModeLabel; }
            set
            {
                timeModeLabel = value;
                RaisePropertyChanged("TimeModeLabel");
            }
        }

        private readonly WadLogLevel[] traceLogLevels =
            new[] { WadLogLevel.Error, WadLogLevel.Warning, WadLogLevel.Info, WadLogLevel.Verbose };

        public WadLogLevel[] TraceLogLevels
        {
            get { return traceLogLevels; }
        }

        private IEnumerable selectedTraceLogLevels;
        public IEnumerable SelectedTraceLogLevels
        {
            get { return selectedTraceLogLevels; }
            set
            {
                selectedTraceLogLevels = value;
                UpdateEventFilter();
                RaisePropertyChanged("SelectedTraceLogLevels");
            }
        }

        private string message;
        public string Message
        {
            get { return message; }
            set
            {
                message = value;
                RaisePropertyChanged("Message");
            }
        }

        private WadLogEntryRowViewModel selectedLogEntry;
        public WadLogEntryRowViewModel SelectedLogEntry
        {
            get { return selectedLogEntry; }
            set
            {
                selectedLogEntry = value;
                RaisePropertyChanged("SelectedLogEntry");
            }
        }

        #endregion

        #region Initialization

        public LogViewerScreenViewModel()
        {

            if (IsInDesignMode)
            {
                SelectedLogEntry = new WadLogEntryRowViewModel(
                    new WadLogEntryDto()
                    {
                        Level = (int)WadLogLevel.Error,
                        DeploymentId = Guid.NewGuid().ToString(),
                        EventId = 9876,
                        Message = Faker.Lorem.Sentence(60),
                        Timestamp = DateTime.UtcNow,
                        Role = "WebRole1",
                        RoleInstance = "WebRole1_IN_1",
                        EventTickCount = DateTime.UtcNow.Ticks,
                        Id = 1,
                        Pid = 3121,
                        Tid = 4534,
                    }, true);
            }
            else
            {
                Main.PropertyChanged += MainPropertyChanged;
                DataStoreService.LogEntriesChanged += DataStoreServiceLogEntriesChanged;
                DataStoreService.WadLogDownloadedSectionsChanged += DataStoreServiceWadLogDownloadedSectionsChanged;

                SelectedDateFilter = DateFilters.First();

                //TODO: Save and load
                LoadData(true, true);

                CreateCommands();
            }

        }

        private void CreateCommands()
        {

            ToggleAutosyncCommand = (ICommand)new RelayCommand(
                () =>
                {
                    Main.LogSyncService.IsEnabled = !Main.LogSyncService.IsEnabled;
                    ToggleAutosyncButtonTitle = Main.LogSyncService.IsEnabled
                                                    ? Resources.ToggleAutosyncButtonTitleDisableAutoSync
                                                    : Resources.ToggleAutosyncButtonTitleEnableAutoSync;
                });

            ToggleLocalTimeCommand = (ICommand)new RelayCommand(
                () =>
                {
                    IsLocalTimeMode = !IsLocalTimeMode;
                    ToggleLocalTimeButtonTitle = IsLocalTimeMode
                                                ? Resources.ToggleLocalTimeButtonTitleShowUTC
                                                : Resources.ToggleLocalTimeButtonTitleShowLocal;
                    TimeModeLabel = IsLocalTimeMode
                                        ? Resources.TimeModeLabelLocalMode
                                        : Resources.TimeModeLabelUTCMode;

                });


            SelectionChangedCommand = (ICommand)
                new RelayCommand<IList>(
                    items =>
                    {
                        if (items == null)
                            return;
                        SelectedLogEntries = items.Cast<WadLogEntryRowViewModel>();
                    });

            KeyDownEventCommand = new RelayCommand<KeyEventArgs>(
                    (args) =>
                    {
                        switch (args.Key)
                        {
                            case Key.F5:
                                Debug.WriteLine("LogViewer: Refresh req" + args.Source);
                                if (RefreshCommand.CanExecute(null))
                                    RefreshCommand.Execute(null);
                                args.Handled = true;
                                break;
                        }
                    });

            LoadedEventCommand = (ICommand)new RelayCommand(ControlLoaded);

            RefreshCommand = (ICommand)
                new RelayCommand(() => Main.LogSyncService.SyncNow(),
                //TODO: Not refreshing!
                                 () => !Main.LogSyncService.IsRefreshing);
        }

        private void ControlLoaded()
        {
            //TODO: Load configuration

            SelectedTraceLogLevels = new[] { WadLogLevel.Error, WadLogLevel.Warning, WadLogLevel.Info, WadLogLevel.Verbose };
        }

        #endregion

        #region Event handlers

        void DataStoreServiceLogEntriesChanged(object sender, EventArgs e)
        {
            Invoke(() => LoadData(false, true));
        }

        void DataStoreServiceWadLogDownloadedSectionsChanged(object sender, EventArgs e)
        {
            Invoke(UpdateMessage);
        }

        void MainPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "SelectedStorageAccount":
                    LoadData(true, true);
                    break;
            }
        }

        #endregion

        #region Data methods

        void LoadData(bool isFilterChanged, bool isDataChanged)
        {
            if (Main.SelectedStorageAccount == null)
            {
                LogEntryItemsProvider = null;
            }
            else
            {
                if (isFilterChanged)
                {
                    //Full reload needed
                    LogEntryItemsProvider = new LogEntryItemsProvider(Main.SelectedStorageAccount.Entity,
                                                                      eventFilter, IsLocalTimeMode);
                }
                else
                {
                    //TODO: Refresh without reloading?
                    LogEntryItemsProvider = new LogEntryItemsProvider(Main.SelectedStorageAccount.Entity,
                                                  eventFilter, IsLocalTimeMode);
                    //LogEntryItemsProvider.RaiseCollectionChanged();

                }
            }
        }

        private void UpdateMessage()
        {
            var info = DataStoreService.GetWadLogDownloadedSectionsInfo(Main.SelectedStorageAccount.Entity,
                eventFilter.StartDate.HasValue ? eventFilter.StartDate.Value : DateTime.MinValue,
                eventFilter.EndDate.HasValue ? eventFilter.EndDate.Value : DateTime.Now);

            if (info.IsFullyDownloaded)
            {
                Message = String.Empty;
            }
            else
            {
                if (info.LastSectionMissingOnly)
                {
                    Message = String.Format(Resources.LogViewerScreenViewModel_UpdateMessage_Last_update,
                                            info.LastEntryDate.HasValue ? info.LastEntryDate.ToString() : "Never (use Refresh)");
                }
                else
                {
                    Message = Resources.LogViewerScreenViewModel_UpdateMessage_Not_all_item_shown;
                }
            }
        }

        private void UpdateEventFilter()
        {
            DateTime startDate;
            DateTime endDate;
            bool isNowEnded;
            bool isChanged = false;

            SelectedDateFilter.CalculateStartAndEndDate(out startDate, out endDate, out isNowEnded);

            if (eventFilter.StartDate != startDate)
            {
                eventFilter.StartDate = startDate;
                isChanged = true;
            }

            if (eventFilter.EndDate != (isNowEnded ? null : (DateTime?)endDate))
            {
                eventFilter.EndDate = isNowEnded ? null : (DateTime?)endDate;
                isChanged = true;
            }

            var levels = SelectedTraceLogLevels == null
                             ? null
                             : SelectedTraceLogLevels.Cast<WadLogLevel>().ToArray();

            if (levels == null)
            {
                if (eventFilter.Levels != null)
                {
                    isChanged = true;
                    eventFilter.Levels = null;
                }
            }
            else
            {
                if (!levels.ArraysEquals(eventFilter.Levels, true))
                {
                    isChanged = true;
                    eventFilter.Levels = levels;
                }
            }

            if (isChanged)
            {
                Debug.WriteLine("EventFilter changed.");
                LoadData(true, false);
            }
        }

        #endregion

        #region Extended RaisePropertyChanged

        private void RaisePropertyChanged(Expression<Func<LogViewerScreenViewModel, object>> selector)
        {
            var propertyName = ((MemberExpression)selector.Body).Member.Name;
            if (!String.IsNullOrEmpty(propertyName))
                RaisePropertyChanged(propertyName);
        }


        #endregion
    }
}
